# Author: Matthew Borneman
# Emulates a microproccessor

def out_regs(out = $stdout)
  out.puts "$zero\t: %04x" % [$registers[0]]
  out.puts "$PC \t: %04x" % [$registers[1]]
  out.puts "$SP \t: %04x" % [$registers[2]]
  out.puts "$A  \t: %04x" % [$registers[3]]
  out.puts "$T0 \t: %04x" % [$registers[4]]
  out.puts "$T1 \t: %04x" % [$registers[5]]
  out.puts "$T2 \t: %04x" % [$registers[6]]
  out.puts "$T3 \t: %04x" % [$registers[7]]
end

def mem_range(range,out = $stdout)
  out.puts "[%04x..%04x]" % [range.first,range.last]
  range.each{|m| if m.even? then out.puts "\t%04x" % [read_mem(m)] end}
end

def out_mem()
  mif = File.new("out_mem.mif","w")
  mif.puts "-- Generated by cc.rb created by Matthew Borneman"
  mif.puts "-- #{Time.now}"
  mif.puts 
  mif.puts "WIDTH=8;"
  mif.puts "DEPTH=4096;"
  mif.puts
  mif.puts "ADDRESS_RADIX=HEX;"
  mif.puts "DATA_RADIX=HEX;"
  mif.puts
  mif.puts "CONTENT BEGIN"
  $MEM.each do |m| 
    mif.puts "\t%04x  :    %02x" % m
  end
  mif.puts "END;"
end

def get_opcode()
  ($IR >> 11) & 0x001F
end

def get_rd()
  ($IR >> 8) & 0x0007
end

def get_rs()
  ($IR >> 5) & 0x0007
end

def get_rt()
  ($IR >> 2) & 0x0007
end

def get_imm(bits)
  r = $IR & (2**bits - 1)
  if $IR[bits-1] == 1
    r |= ~(2**bits - 1)
  end
  return r
end

def get_s1()
  ($IR & 2) != 0
end

def get_s0()
  ($IR & 1) != 0
end

def read_mem(addr)
  #puts addr&0x0000FFFF
  ($MEM[(addr+1)&0x0000FFFF] << 8) | ($MEM[addr&0x0000FFFF])
end

def write_mem(addr,val)
  $MEM[addr&0x0000FFFF] = val & 0x00FF
  $MEM[(addr+1)&0x0000FFFF] = (val >> 8) & 0x00FF
end

$trace = File.new("trace.txt","w")
def trace_f(ir)
  $trace.puts "#{$T[get_opcode]}\t#{(ir >> 8)&7}\t#{(ir >> 5)&7}\t#{(ir >> 2)&7}\t#{(ir)&0xFF}"
  out_regs($trace)
end

def stack_trace(stack,size)
  if(size > 0)
    mem_range(((stack-size)&0x7FFF)..((stack-1)&0x7FFF),$trace)
  end
end

$OP = {}
$T = {}
op_file = File.open("OPCODE.txt","r")
while line = op_file.gets
  /(?<num>\d+)\s+(?<mne>\w+)/ =~ line
  $OP[mne.to_sym] = num.to_i
  $T[num.to_i] = mne.to_sym
end

$MEM = {}
mif_file = File.open(ARGV[0],"r")
while !(/CONTENT BEGIN/ === line = mif_file.gets) 
  puts line
end
line = mif_file.gets
while (line != nil) && !(/END;/ === line)
  /(?<addr>\w+)\s*:\s*(?<val>\w+)/ =~ line
  puts line
  if(addr != nil && val != nil)
    $MEM[addr.hex] = val.hex
  else
    /\[(?<addr1>\w+)\.\.(?<addr2>\w+)\]\s*:\s*(?<val1>\w+)/ =~ line
    (addr1.hex..addr2.hex).each{|a| $MEM[a] = val1.hex}
  end
  line = mif_file.gets
end
out_mem
$registers = [0,0,0,0,0,0,0,0]

$IR = 0
$C = false
stack_size = 0
cycles = 0;
10000000.times do
  #puts $T[get_opcode]
  #puts $registers[1].to_s(16)
  $IR = read_mem($registers[1])
  $registers[1] += 2
  cycles += 3
  #$trace.puts "%04x" % [$IR]
  # $trace.puts "PC: %04x" % [$registers[1]]
  # $trace.puts "SP: %04x" % [$registers[2]]
  # $trace.puts "--------"
  
  trace_f($IR)
  stack_trace($registers[2],stack_size)
  case get_opcode
  when $OP[:ADD]
      $registers[get_rd] = $registers[get_rs] + $registers[get_rt]
      if get_s1 && $C
        $registers[get_rd] += 1
      end
      $C = ($registers[get_rd] & 0x10000) != 0
      $registers[get_rd] &= 0x0000FFFF
  when $OP[:SUB]
      $registers[get_rd] = $registers[get_rs] - $registers[get_rt]
      if get_s1 && $C
        $registers[get_rd] += 1
      end
      $C = ($registers[get_rd] & 0x10000) != 0
      $registers[get_rd] &= 0x0000FFFF
  when $OP[:MUL]
      $registers[get_rd] = $registers[get_rs] * $registers[get_rt]
      $registers[get_rd] &= 0x0000FFFF
  when $OP[:DIV]
      if get_s0
        $registers[get_rd] = $registers[get_rs] % $registers[get_rt]
      else
        $registers[get_rd] = $registers[get_rs] / $registers[get_rt]
      end
      $registers[get_rd] &= 0x0000FFFF
  when $OP[:bAND]
      $registers[get_rd] = $registers[get_rs] & $registers[get_rt]
      $registers[get_rd] &= 0x0000FFFF
  when $OP[:bOR]
      $registers[get_rd] = $registers[get_rs] | $registers[get_rt]
      $registers[get_rd] &= 0x0000FFFF
  when $OP[:bXOR]
      $registers[get_rd] = $registers[get_rs] ^ $registers[get_rt]
      $registers[get_rd] &= 0x0000FFFF
  when $OP[:INC]
      if get_s1
        $registers[get_rd] = $registers[get_rs] - 1
      else
        $registers[get_rd] = $registers[get_rs] + 1
      end
      $registers[get_rd] &= 0x0000FFFF
  when $OP[:SL]
      $registers[get_rd] = $registers[get_rs] << $registers[get_rt]
      $registers[get_rd] &= 0x0000FFFF
  when $OP[:SR]
      $registers[get_rd] = $registers[get_rs] >> $registers[get_rt]
      $registers[get_rd] &= 0x0000FFFF
  when $OP[:LI]
      $registers[get_rd] = get_imm(8)

  when $OP[:LW]
      $registers[get_rd] = read_mem($registers[get_rs]+(get_imm(5)<<1))
      cycles += 1
  when $OP[:LB]
      $registers[get_rd] = ($MEM[$registers[get_rs]+get_imm(5)])
      cycles += 1
  when $OP[:SW]
     write_mem($registers[get_rs]+(get_imm(5)<<1),$registers[get_rd])
  when $OP[:SB]
      $MEM[$registers[get_rs]+get_imm(5)] = $registers[get_rd] & 0x00FF
  when $OP[:EQU]
    if ($registers[get_rs] == $registers[get_rt]) != get_s0
      $registers[get_rd] = 1
    else
      $registers[get_rd] = 0
    end
  when $OP[:LT]
      if ($registers[get_rs] < $registers[get_rt]) != get_s0
        $registers[get_rd] = 1
      else
        $registers[get_rd] = 0
      end
  when $OP[:LTE]
      if ($registers[get_rs] <= $registers[get_rt]) != get_s0
        $registers[get_rd] = 1
      else
        $registers[get_rd] = 0
      end
  when $OP[:SKIZ]
      if $registers[get_rt] == 0
        $registers[1] += 2
      end
  when $OP[:SKNZ]
      if $registers[get_rt] != 0
        $registers[1] += 2
      end
  when $OP[:SKP]
      $registers[1] += 2
  when $OP[:PSH]
      write_mem($registers[get_rs],$registers[get_rt])
      $registers[2] += 2
      stack_size += 2
      cycles += 1
  when $OP[:POP]
    $registers[2] -= 2
    $registers[get_rd] = read_mem($registers[get_rs])
    stack_size -= 2
    cycles += 2
  when $OP[:LEA]
    $registers[get_rd] += get_imm(8)
    $registers[get_rd] &= 0x0000FFFF
    if(get_rd == 2)
      stack_size += get_imm(8)
    end
  when $OP[:JAL]
    write_mem($registers[get_rs],$registers[get_rd])
    $registers[2] += 2
    $registers[get_rd] = $registers[get_rt]
    stack_size += 2
    cycles += 1
  when $OP[:AND]
    if ($registers[get_rs] != 0) && ($registers[get_rt] != 0)
      $registers[get_rd] = 1
    else
      $registers[get_rd] = 0
    end
  when $OP[:OR]
    if ($registers[get_rs] != 0) || ($registers[get_rt] != 0)
      $registers[get_rd] = 1
    else
      $registers[get_rd] = 0
    end
  when $OP[:NOT]
    if ($registers[get_rs] == 0)
      $registers[get_rd] = 1
    else
      $registers[get_rd] = 0
    end
  when $OP[:JREL]
    $registers[1] += get_imm(11)<<1
  when 0x001F
    out_regs
    puts "Cycles: #{cycles}"
    #mem_range(0x800..0x809)
    break
  end
  $registers.each {|r| 
    if r == nil
      puts "NIL REG : #{$T[get_opcode]} : %04x %04x" % [$IR,$registers[1]]
    end
  }
end

